﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommServer;
using CommServer.Common;
using CommServer.Protocol;

namespace CommServer.Upgrade
{
    public class upgradeInv
    {
        private string _RtuSN;
        private int _section;
        private int _totalBytes;
        private int _totalBlocks;
        private int _currentBlockNo;
        private string _fileName;
        private int _blockSize = 1024;
        private byte[] fileContents;
        private int _transferStatus = 0;//0未启动 1 开始传输 2 开始校验 3校验  4 1阶段传输结束 5.1阶段传输失败 6 2阶段传输中 7 2阶段传输结束 8 2阶段升级确认
        private int _totalRetryLimit = 150;
        private int _totalRetryCount = 0;
        private int _stepRetryLimit = 5;
        private int _stepRetryCount = 0;
        private int _interval = 200;//0.1秒
        private int _versionToUpgrade = 0;
        private int _step;//0表示程序从服务器到采集器，1表示程序从采集器到逆变器。
        private int _versionInDevice = 0;
        private int _fileCS = 0;
        private DeviceServer deviceServer;
        private int programId;
        private Action<byte[]> receiveAction;
        private DateTime _lastSendTime;
        private System.Threading.Thread checkThread;
        private bool _stopChecking = false;
        private bool upgrading = false;
        private bool _completed = false;
        private int _verifyCode = 1;
        private string _ErrorMsg;
        public event Action<int> onTiming;
        public event Action<string> onTimeout;
        public event Action<string, dynamic> onComplete;

        public upgradeInv(string RTUSN, string fileName)
        {

            _RtuSN = RTUSN;
            receiveAction = new Action<byte[]>(receiveFrameDo);
            _fileName = fileName;
            checkThread = new System.Threading.Thread(new ThreadStart(checkDo));
            deviceServer = Program.GetDeviceServer();
            deviceServer.OnMaintainFrameReceive += receiveAction;
        }
        ~upgradeInv()
        {
            deviceServer.OnMaintainFrameReceive -= receiveAction;
        }
        public string RtuSN { get => _RtuSN; set => _RtuSN = value; }
        public int Section { get => _section; set => _section = value; }
        public int TotalBytes { get => _totalBytes; }
        public int TotalBlocks { get => _totalBlocks; }
        public int CurrentBlockNo { get => _currentBlockNo; set => _currentBlockNo = value; }

        public string FileName { get => _fileName; set { _fileName = value; loadFile(value); } }
        public int Version { get => _versionToUpgrade; set => _versionToUpgrade = value; }
        public bool Upgrading { get => upgrading; set => upgrading = value; }
        public bool Completed { get => _completed; set => _completed = value; }
        public int ProgramId { get => programId; set => programId = value; }
        public int TransferStatus { get => _transferStatus; }
        public string ErrorMsg { get => _ErrorMsg; }
        public int FileCS { get => _fileCS; }
        public int Step { get => _step; }
        public int TotalRetryCount { get => _totalRetryCount; set => _totalRetryCount = value; }
        public int StepRetryCount { get => _stepRetryCount; set => _stepRetryCount = value; }
        private bool _debug = false;
        public void stopUpgrade()
        {
            stopCheck();
        }

        public void loadFile()
        {
            loadFile(_fileName);
        }
        private void loadFile(string fileName)
        {
            _fileName = fileName;
            FileStream fs = new FileStream(fileName, FileMode.Open);
            fs.Seek(0, SeekOrigin.Begin);
            fileContents = new byte[fs.Length];
            fs.Read(fileContents, 0, fileContents.Length);
            fs.Close();
            fs.Dispose();
            _totalBytes = fileContents.Length;
            _currentBlockNo = 0;
            _totalBlocks = ((_totalBytes % _blockSize) == 0) ? _totalBytes / _blockSize : ((_totalBytes / _blockSize) + 1);
            _fileCS = 0;
            for (int i = 0; i < fileContents.Length; i++)
            {
                _fileCS = (_fileCS + fileContents[i]) & 0xffff;
            }

        }

        private void Send(byte[] Frame)
        {
            byte[] maintainFrame = ProtocolPV.BuildMaintainFrame(0xF6, Frame);
            Log.Debug("Send:" + Util.BytesToHex(maintainFrame));
            deviceServer.Send(_RtuSN, maintainFrame);
            
                _lastSendTime = DateTime.Now;
                _stopChecking = false;
                startCheck();

            
        }
        private void checkDo()
        {
            while (true)
            {
                if (_stopChecking)
                    return;
                DateTime now = DateTime.Now;
                TimeSpan ts = now - _lastSendTime;
                if (ts.TotalMilliseconds > _interval * 100)
                {
                    _stopChecking = true;
                    Log.Debug("Timeout:【retry-Total】" + _totalRetryCount + "/" + _totalRetryLimit + ",【retry-current】" + _stepRetryCount + "/" + _stepRetryLimit);
                    onTimeout?.Invoke("Timeout:【retry-Total】" + _totalRetryCount + "/" + _totalRetryLimit + ",【retry-current】" + _stepRetryCount + "/" + _stepRetryLimit);
                    performTimeout();
                    return;
                }
                else
                {
                    onTiming?.Invoke((int)ts.TotalSeconds);
                    Thread.Sleep(100);
                }
            }

        }
        public void cancelUpgrade()
        {
            _transferStatus = 9;
            _step = 0;
            _currentBlockNo = 0;
            upgrading = false;
            _completed = true;
            //verify(2);
        }
    	public void closeUpgrade()
        {
            _transferStatus = 0;
            _step = 0;
            _currentBlockNo = 0;
            upgrading = true;
            _completed = false;
            verify(2);
        }
        private void performTimeout()
        {
            if (_debug)
            {
                if (_currentBlockNo < _totalBlocks - 1)
                {//如果还没有传完，继续传
                    _currentBlockNo++;
                    upgrade(_currentBlockNo);
                }
                else
                {//传完了，查状态。
                    _transferStatus = 2;
                    queryStatus();
                }
                return;
            }


            _totalRetryCount++;
            _stepRetryCount++;
            if ((_stepRetryCount > _stepRetryLimit) || (_totalRetryCount > _totalRetryLimit))
            {
                upgrading = false;
                _completed = true;
                _ErrorMsg = "超时，且超过重试次数";
                Log.Debug("超时，且超过重试次数");
                onComplete?.Invoke(_RtuSN, new { success = false, errMsg = _ErrorMsg, obj = this });
            }
            else
            {
                if ((_transferStatus == 0) || (_transferStatus == 2))
                {
                    queryStatus();
                }
                else if (_transferStatus == 1)
                {
                    upgrade(_currentBlockNo);
                }
                else if (_transferStatus == 3)
                {
                    verify(_verifyCode);
                }
            }
        }

        private void startCheck()
        {
            _stopChecking = false;
            checkThread = new Thread(new ThreadStart(checkDo));
            checkThread.Start();
        }
        private void stopCheck()
        {
            _stopChecking = true;
        }
        private void receiveFrameDo(byte[] Frame)
        {
            Dictionary<string, object> Result = new Dictionary<string, object>();
            ProtocolPV.FrameToObject(Frame, Result);
            int controlCode = (int)Result["controlCode"];
            stopCheck();
            if (controlCode != 0xF6)
                return;
            if ((!upgrading) || (_completed))
                return;
            Log.Debug("Recv:" + Util.BytesToHex(Frame));
            int tot = (int)Result["total"];
            int cur = (int)Result["current"];
            int dataLen = (int)Result["innerDataLen"];
            byte[] dataFrame = null;
            if (dataLen > 0)
            {
                dataFrame = Result["dataFrame"] as byte[];
            }
            _stepRetryCount = 0;
            switch (tot)
            {
                case 0xffff:
                    if (cur == 0)
                    {

                        _step = dataFrame[0];
                        int totInDevice = dataFrame[1] * 256 + dataFrame[2];
                        int curInDevice = dataFrame[3] * 256 + dataFrame[4];
                        int bytesInDevice = dataFrame[5] * 256 * 256 + dataFrame[6] * 256 + dataFrame[7];
                        int csInDevice = dataFrame[8] * 256 + dataFrame[9];

                        if (_step == 0)
                        {

                            if (totInDevice == 0)
                            {//设备里记录未下载，无论当前什么状态，都要重新开始下载
                                reUpgrade();
                            }
                            else
                            {
                                if (totInDevice != _totalBlocks)
                                {
                                    reUpgrade();
                                    return;
                                }
                                if (curInDevice != _currentBlockNo)
                                {
                                    _currentBlockNo = curInDevice + 1;
                                    upgrade(_currentBlockNo);
                                    return;
                                }
                                if (totInDevice == (_currentBlockNo + 1))
                                {
                                    if (_transferStatus == 2)
                                    {//传完了，校验
                                        _transferStatus = 3;
                                        if ((bytesInDevice == _totalBytes) && (csInDevice == _fileCS))
                                        {
                                            _verifyCode = 1;
                                        }
                                        else
                                        {
                                            _verifyCode = 2;
                                        }
                                        Thread.Sleep(2000);
                                        verify(_verifyCode);
                                    }
                                    else if (_transferStatus==4)
                                    {
                                        Thread.Sleep(5000);
                                        queryStatus();
                                    }
                                }
                                else
                                {
                                    if (_transferStatus == 0)
                                    {//开始的时候查询，如果设备内已经传了，则继续传
                                        loadFile();
                                        _currentBlockNo = curInDevice + 1;
                                        upgrade(_currentBlockNo);
                                    }
                                    else if (_transferStatus == 1)
                                    {//运行过程中查询状态，返回后继续传
                                        _currentBlockNo = curInDevice + 1;
                                        upgrade(_currentBlockNo);
                                    }
                                }
                            }
                        }
                        else
                        {
                            _currentBlockNo = curInDevice;
                            Thread.Sleep(5000);
                            if (curInDevice >= (_totalBlocks - 1))
                            {
                                
                                _transferStatus = 7;
                                verify(2);
                               
                            }
                            else
                            {
                                _transferStatus = 6;
                                queryStatus();
                            }
                        }
                    }
                    else if (cur == 1)
                    {
                        _transferStatus = 4;
                        Thread.Sleep(5000);
                        //_step = 1;
                        queryStatus();
                    }
                    else
                    {
                        if (_transferStatus == 0)
                        {
                            queryStatus();
                        }
                        else if(_transferStatus==7)
                        {
                            _transferStatus = 8;
                            _completed = true;
                            upgrading = false;
                            //_step = 0;
                            //_currentBlockNo = 0;
                            onComplete?.Invoke(_RtuSN, new { success = true, errMsg = "", obj = this });
                        }
                        else if (_transferStatus == 9)
                        {
                            upgrading = false;
                            _completed = true;
                        }
                        else
                        {
                            _transferStatus = 5;
                            upgrading = false;
                            _completed = true;
                            _ErrorMsg = "校验失败！";
                            Log.Error(_ErrorMsg);
                            onComplete?.Invoke(_RtuSN, new { success = false, errMsg = _ErrorMsg, obj = this });
                        }
                    }
                    break;
                default:
                    if ((tot == _totalBlocks) && (cur == _currentBlockNo))
                    {//总块号相同，当前块号相同，说明传成功。

                        if (_currentBlockNo < _totalBlocks - 1)
                        {//如果还没有传完，继续传
                            _currentBlockNo++;
                            upgrade(_currentBlockNo);
                        }
                        else
                        {//传完了，查状态。
                            _transferStatus = 2;
                            queryStatus();
                        }
                    }
                    else
                    {
                        if (tot == _totalBlocks)
                        {//总包数相等，但是传回的包数不一致，则认为设备上报的帧未传成功，重传。
                            _currentBlockNo = cur + 1;
                            upgrade(_currentBlockNo);
                        }
                        else
                        {//传着传着，设备上报的总包数不对了。强制失败，不再重传
                            _transferStatus = 5;
                            _ErrorMsg = "更新过程中，设备回复的包数和下发包数不一致";
                            Log.Error(_ErrorMsg);
                            onComplete?.Invoke(_RtuSN, new { success = false, errMsg = _ErrorMsg, obj = this });
                            _completed = true;
                            upgrading = false;
                        }
                    }
                    break;
            }
        }
        private void reUpgrade()
        {
            upgrading = true;
            _currentBlockNo = 0;
            _stepRetryCount = 0;
            loadFile();
            upgrade(_currentBlockNo);
        }
    
        private void verify(int status)
        {
            byte[] Frame = buildFrame(0xffff, status, null);
            Send(Frame);
        }

        public void startUpgrade(bool closeFirst )
        {          
            _transferStatus = 0;//先查一下
            _totalRetryCount = 0;
            _stepRetryCount = 0;
            upgrading = true;
            _completed = false;
            if (closeFirst)
            {
                closeUpgrade();
            }
            else
            {
                queryStatus();
            }
        }

        public void makeFrames()
        {
            for (int i = 0; i < _totalBlocks; i++)
            {
                byte[] data = getDataBlock(i);
                byte[] Frame = buildFrame(_totalBlocks, i, data);
                byte[] maintainFrame = ProtocolPV.BuildMaintainFrame(0xF6, Frame);
                Log.Debug("Frames " + i + ":" + Util.BytesToHex(maintainFrame));
                Thread.Sleep(200);
            }
        }

        public void queryStatus()
        {
            int total = 0xffff;
            int current = 0;
            byte[] Frame = buildFrame(total, current, null);
            Send(Frame);
        }
        private void upgrade(int blockno)
        {
            upgrading = true;
            _transferStatus = 1;
            byte[] data = getDataBlock(blockno);
            byte[] Frame = buildFrame(_totalBlocks, blockno, data);
            Send(Frame);
        }

        private byte[] getDataBlock(int blockno)
        {
            int dataLen = _blockSize;
            if (blockno == (_totalBlocks - 1))
            {
                dataLen = _totalBytes - blockno * _blockSize;
            }
            byte[] data = new byte[dataLen];
            Buffer.BlockCopy(fileContents, _blockSize * blockno, data, 0, dataLen);
            return data;
        }

        private byte[] buildFrame(int total, int current, byte[] data)
        {
            List<byte> Result = new List<byte>();
            Result.Add((byte)(total >> 8));
            Result.Add((byte)total);
            Result.Add((byte)(current >> 8));
            Result.Add((byte)current);
            int dataLen = (data == null) ? 0 : data.Length;
            Result.Add((byte)(dataLen >> 8));
            Result.Add((byte)dataLen);
            if (data != null)
            {
                Result.AddRange(data);
            }
            ushort crc = Util.CalCRC16A(Result, 0, Result.Count);
            Result.AddRange(BitConverter.GetBytes(crc));
            return Result.ToArray();
        }

    }
}
